/** 
 * Document Binding Engine
 *
 * @author benoit.pointet
 *
 * @version 0.2
 *
 * Javascript Interpreter for Document Binding Language Descriptions.
 */

if (!Array.prototype.indexOf)
{
    Array.prototype.indexOf = function(elt /*, from*/)
    {
        var len = this.length >>> 0;

        var from = Number(arguments[1]) || 0;
        from = (from < 0) ? Math.ceil(from) : Math.floor(from);
        if (from < 0)
            from += len;

        for (; from < len; from++)
        {
            if (from in this && this[from] === elt)
                return from;
        }
        return -1;
    };
}

/* Document Binding Engine *********************************/

var $dbe = (function()
{

    // *** DEFINITIONS ***
    var dbe = {
        dbl: {},
        // dbl management
        op: {},
        // operational management
        evl: {},
        // expression evaluation system
        dom: {},
        // document management
        src: {},
        // sources management
        evt: {},
        // event system
        fun: {},
        // helper functions
        log: {},
        // logging, statistics and monitoring functions
        api: {}
    // api functions
    };

    // optimizing accesses
    var dbl = dbe.dbl;
    var op = dbe.op;
    var evl = dbe.evl;
    var dom = dbe.dom;
    var src = dbe.src;
    var evt = dbe.evt;
    var fun = dbe.fun;
    var log = dbe.log;
    var api = dbe.api;

    // *** dbe.dbl ************
    // dbl type constants
    dbl.BIND = 'bind';
    dbl.NODE = 'node';
    dbl.RANGE = 'range';

    /**
     * dbl.N constructor
     * @return new dblN
     */
    dbl.N = function(xmlElt, parDblN) {
        log.register(this);
        this.events = new evt.EventHandler(this);
        this.state = new op.State(this);
        // sets given parent, if none and given refing, inherits parent
        this.parDblN = parDblN;
        this.defDblN = null;
        this.scope = new op.Scope(this.parDblN ? this.parDblN.scope : null);
        this.params = {};
        this.xmlElt = xmlElt;
        this.dblNs = {};
        this.dblNs[dbl.BIND] = new fun.Set();
        this.dblNs[dbl.NODE] = new fun.Set();
        this.dblNs[dbl.RANGE] = new fun.Set();

        // construction of sub dblNs and hrefed dblNs
        if (this.xmlElt) {
            var that = this;
            // constructs nested dbl elements
            for (var i = 0; i < this.xmlElt.childNodes.length; i++) {
                var childXmlNode = this.xmlElt.childNodes.item(i);
                if (childXmlNode.nodeType === 1) {
                    // if it's an xml element node
                    new dbl.N(childXmlNode, that);
                }
            }
        }

        // local definition reuse
        if (this.get('def')) {
            // query ancestors to find like id and use it as def            
            this.defDblN = this.scope.get(this.get('def')).first();
        }
        else if (this.get('hdef')) { // global definition reuse
            var defXmlElt = src.adapters['dbl'].loadSrc(this.get('hdef')).documentElement;
            this.defDblN = new dbl.N(defXmlElt,null);
        }

        if (this.parDblN) {
            // adds as child of parent node
            this.parDblN.addDblN(this);
        }

    };

    /**
     * @return the named (string) type of this dblN, default is dbl.BIND
     */
    dbl.N.prototype.type = function() {
        return (this.xmlElt ? this.xmlElt.nodeName: dbl.BIND);
    }

    /**
     * @return wether this dblN is of given type
     */
    dbl.N.prototype.is = function(type) {
        return (this.type() === type);
    }

    /**
     * default values when getting dblN params
     */
    dbl.N.settings = {
        'bind': {
            id:   {
                value:'',
                inherit:true,
                cascade:false
            },
            def:  {
                value:'',
                inherit:true,
                cascade:false
            },
            hdef: {
                value:'',
                inherit:true,
                cascade:false
            },
            'query-type' : {
                value:'',
                inherit:false,
                cascade:false // STOP query-type cascading
            },
            'src-type' : {
                value:'',
                inherit:false,
                cascade:false // STOP src-type cascading
            }
        },
        'node': {
            id: {
                value:'',
                inherit:true,
                cascade:false
            },
            def: {
                value:'',
                inherit:true,
                cascade:false
            },
            hdef: {
                value:'',
                inherit:true,
                cascade:false
            },
            query: {
                value:'',
                inherit:true,
                cascade:false
            },
            src: {
                value:'',
                inherit:true,
                cascade:false
            },
            'query-type' : {
                value:'',
                inherit:true,
                cascade:true // query-type cascading : reuse of adapter
            },
            'src-type' : {
                value:'',
                inherit:true,
                cascade:true // src-type cascading : reuse of adapter
            },
            ref: {
                value:'',
                inherit:true,
                cascade:false
            },
            match: {
                value:'',
                inherit:true,
                cascade:false
            },
            miss: {
                value:'',
                inherit:true,
                cascade:false
            },
            min: {
                value:'1',
                inherit:true,
                cascade:false
            },
            max: {
                value:'1',
                inherit:true,
                cascade:false
            },
            adapt: {
                value:'',
                inherit:true,
                cascade:false
            }
        },
        'range': {
            id: {
                value:'',
                inherit:true,
                cascade:false
            },
            def: {
                value:'',
                inherit:true,
                cascade:false
            },
            hdef: {
                value:'',
                inherit:true,
                cascade:false
            },
            values: {
                value:'',
                inherit:true,
                cascade:false
            },
            min: {
                value:'0',
                inherit:true,
                cascade:false
            },
            max: {
                value:'1',
                inherit:true,
                cascade:false
            },
            order: {
                value:'',
                inherit:true,
                cascade:false
            },
            step: {
                value:'1',
                inherit:true,
                cascade:false
            },
            unit: {
                value:'',
                inherit:true,
                cascade:false
            }
        }
    };

    /**
     * @return this dblN param's of name p, or default value.
     * uses caching of xmlAttribute values in params field.
     * TODO 0.3 integrate computation of { ... } expressions
     */
    dbl.N.prototype.get = function(p) {
        var params = this.params;
        // console.log('dbl.N.get',p,this.xmlElt);
        if (params[p]) {
            return params[p];
        }
        var v;
        if (this.xmlElt && (v = this.xmlElt.getAttribute(p))) {            
            return params[p] = v;
        }
        var settings = dbl.N.settings[this.type()][p];
        // first defDblN inheritance
        if (settings) {
            if (settings.inherit && this.defDblN && (v = this.defDblN.get(p))) {
                return params[p] = v;
            }
            // else cascading
            if (settings.cascade && this.parDblN && (v = this.parDblN.get(p))) {
                return params[p] = v;
            }
            // else settings default value
            if ((v = settings.value)) {
                return params[p] = v;
            }
        }
        return params[p] = '';
    }

    /**
     * @return wether dblN has attr of name p is set.
     */
    dbl.N.prototype.has = function(p) {
        return (this.xmlElt && (this.xmlElt.getAttribute(p)));
    }

    /**
     * sets this dblN's param p to value v
     * @return void
     */
    dbl.N.prototype.set = function(p, v) {
        this.xmlElt.setAttribute(p, v);
        this.params[p] = v;
        this.events.fire('DblNModified');
    }

    /**
     * @return the dblN ancestor or itself that has given id
     */
    dbl.N.prototype.query = function(id) {
        if (this.get('id') === id) {
            return this;
        }
        if (this.parDblN) {
            return this.parDblN.query(id);
        }
        return null;
    }

    /**
     * collects all child dblNs and the children of its ref
     * @return void
     */
    dbl.N.prototype.getDblNs = function() {
        if (this.defDblN) {
            var defDblNs = this.defDblN.getDblNs();
            var dblNs = {};
            dblNs[dbl.BIND] = this.dblNs[dbl.BIND].union(defDblNs[dbl.BIND]);
            dblNs[dbl.NODE] = this.dblNs[dbl.NODE].union(defDblNs[dbl.NODE]);
            dblNs[dbl.RANGE] = this.dblNs[dbl.RANGE].union(defDblNs[dbl.RANGE]);
            return dblNs;
        }
        return this.dblNs;
    }

    /**
     * adds uniquely a sub dblN to this dblN
     * @return void
     */
    dbl.N.prototype.addDblN = function(dblN) {
        var type = dblN.type();
        // console.log(this.dblNs,dblN,type);
        if (this.dblNs[type].add(dblN)) {
            if (dblN.get('id')) {
                this.scope.add(dblN.get('id'),dblN);
            }
            this.events.fire(new evt.Event('DblNAdded', this, {
                addedDblN: dblN
            }));
        }
    }

    /**
     * removes if exists a sub dblN of this dblN
     * @return void
     */
    dbl.N.prototype.remDblN = function(dblN) {
        var type = dblN.type();
        if (this.dblNs[type].rem(dblN)) {
            this.events.fire(new evt.Event('DblNRemoved', this, {
                removedDblN: dblN
            }));
        }
    }

    /* dbe.op *************************************/

    // the root opB
    op.rootOpB = null;
    op.theoricalRanges  = {};

    /**
     * loads a distant binding document, and add it to rootDblB as sub dblN
     * @returns nothing
     */
    op.addRootBinding = function(binding) {
        log.time['addBinding'] = new Date().getTime();
        if (typeof binding === 'string') {
            src.adapters['dbl'].loadSrc(binding,
                function(xmlDoc) {
                    log.time['rootDblN'] = new Date().getTime();
                    new dbl.N(xmlDoc.documentElement, op.rootOpB.dblB);
                });
        }
    };

    /**
     * op.Scope constructor
     * @return new op.Scope
     */
    op.Scope = function(parScope) {
        log.register(this);
        this.parScope = parScope;
        this.bag = {};
    }

    /**
     * @return a Set containing opN's which match the given dotQuery
     */
    op.Scope.prototype.get = function(dq,forward) {
        forward = forward ? forward : false;
        // locks submitting query to parent
        // forward = forward ? true: false;
        var result;
        if (dq.indexOf('|') > -1) {
            dq = dq.split('|');
            result = new fun.Set();
            for (var i=0; i<dq.length;i++) {
                result.join(this.get(dq[i],forward));
            }
        } else {
            dq = dq.split('.');
            var head = dq[0];
            var headSet;
            if (head === '*') {
                headSet = this.getAll();
            } else {
                if (head.indexOf('[') > -1) {
                    var cut = head.indexOf('[');
                    var filter = head.substring(cut+1,head.length-1);
                    head = head.substring(0,cut);
                    cut = filter.indexOf('=');
                    var filterNodeStr = filter.substring(0,cut);
                    var filterValueStr = filter.substring(cut+1);
                // console.log('[[[',head,head.length,typeof head,filterNodeStr,filterValueStr,this.uid,this.bag[head]);
                }
                headSet = this.bag[head];
                if (filterNodeStr && filterValueStr) {
                    var newSet = new fun.Set();
                    var that = this;
                    headSet.each(function(node){
                        var filterNode = node.scope.get(filterNodeStr).first();
                        if (filterNode && filterNode.getValue) {
                            var filterNodeValue = filterNode.getValue();
                            var filterValue = evl.evaluate(filterValueStr,that);
                            if (filterNodeValue == filterValue) {
                                newSet.add(node);
                            // console.log(that.bag,filterNode,filterNodeValue,'==',filterValue, headSet.size(), newSet.size(),newSet.uid,dq);
                            }
                        }
                    });
                    headSet = newSet;
                }
            }
            if (headSet && headSet.size && headSet.size() === 0) {
                headSet = null;
            }
            if (headSet) {
                // if first word in dotQuery is locally known
                if (dq.length === 1) {
                    // if it's a simple query
                    result = headSet;
                } else {
                    // else query has to be pursued by head set
                    dq.shift();
                    result = new fun.Set();
                    headSet.each(function(item) {
                        result.join(item.scope.get(dq.join('.'),true));
                    });
                }
            } else {
                // if word is not locally known, ask parent if not forward flag
                // console.log('op.Scope.ASK',this.uid,dq,forward,this);

                if (!forward && this.parScope) {
                    result = this.parScope.get(dq.join('.'),forward);
                }
            }
        }
        result = result ? result : new fun.Set();
        return result;
        
    }

    /**
     * @return all items contained in this scope
     */
    op.Scope.prototype.getAll = function() {
        var all = new fun.Set();
        var set;
        var self = this.bag['_self'];
        for (var p in this.bag) {
            set = this.bag[p];
            if (set.size && p.indexOf('_')!==0 && !(self && self.has(set.first()))) { // TODO 0.3 refine this dirty check for set equality
                all.join(set);
            }
        }
        return all;
    }

    /**
     * set this scope's member with name id to given fun.Set s
     * @return nothing
     */
    op.Scope.prototype.set = function(id, s) {
        this.bag[id] = s;
    }

    /**
     * add to this scope's set with name id the given item
     * @return nothing
     */
    op.Scope.prototype.add = function(id, item) {
        if (!this.bag[id]) {
            this.bag[id] = new fun.Set();
        }
        this.bag[id].add(item);
        this.bag['_latest'] = item; // TODO 0.3 refactor _latest as a list (with non unique items)
    // console.log('op.Scope.add',id,this.bag[id].size());
    }

    /**
     * rem to this scope's set with name id the given item
     * @return nothing
     * TODO 0.3 unittest op.Scope.rem()
     */
    op.Scope.prototype.rem = function(id, item) {
        if (this.bag[id] && this.bag[id].has(item)) {
            this.bag[id].rem(item);
            this.bag['_latest'] = null;
        }
    }
    
    /**
     * unset (nullify and remove) this scope's member set with name id
     * @return nothing
     */
    op.Scope.prototype.unset = function(id) {
        this.bag[id] = null;
        delete this.bag[id];
    }


    /**
     * op.State constructor
     * @returns a new op.State
     */
    op.State = function(host) {
        log.register(this);
        this.s = 0;
        this.host = host;
        this.updateCount = -1;
        this.toUpdateCount = 0;
        this.updateStamp = 0;
    }
    op.State.prototype.UPDATING = 10;
    op.State.prototype.UPDATED = 0;
    op.State.prototype.nowUpdating = function() {
        this.s = this.UPDATING;
    // console.log('nowUpdatING',this.updateCount,this.toUpdateCount,this.host.dblN?this.host.dblN.xmlElt:'',this.host.dblB?this.host.dblB.xmlElt:'');
    }
    op.State.prototype.nowUpdated = function() {
        this.s = this.UPDATED;
        this.updateCount = this.updateCount + 1;
        if (this.updateCount > this.toUpdateCount) {
            this.toUpdateCount = this.updateCount;
        }
        if (this.updateCount < this.toUpdateCount) {
            this.updateCount = this.toUpdateCount-1;
        }
        // if (this.host.isRoot) console.log('nowUpdatED ',this.updateCount,this.toUpdateCount,this.host.dblN?this.host.dblN.xmlElt:'',this.host.dblB?this.host.dblB.xmlElt:'');
        this.updateStamp = new Date().getTime();
    }
    op.State.prototype.toUpdate = function() {
        this.toUpdateCount = this.toUpdateCount + 1;
    // console.log('TOUpdate  ',this.updateCount,this.toUpdateCount,this.host.dblN?this.host.dblN.xmlElt:'',this.host.dblB?this.host.dblB.xmlElt:'');
    }
    op.State.prototype.isUpdating = function() {
        return (this.s === this.UPDATING);
    }
    op.State.prototype.isUpdated = function() {
        return (this.s === this.UPDATED);
    }
    op.State.prototype.isToUpdate = function() {
        return (this.updateCount < this.toUpdateCount);
    }


    /**
     * op.Range constructor
     * @returns a new op.Range
     */
    op.Range = function(dblN,host) {
        // console.log('op.Range',dblN.xmlElt,host);
        log.register(this);
        this.dblN = dblN;
        this.state = new op.State(this);
        this.host = host;
        var id = this.get('id');
        if (id) {
            op.Range.defs[id] = this.dblN; // injects dblN at root to be refed
        }
        var def = this.get('def');
        if (def) {
            this.dblN.defDblN = op.Range.defs[def]; // injects dblN at root to be refed
        }
        // TODO 0.3 refine op.Ranges references:
        // create a range interface
        // define a range hierarchy
        // add many op.Range's in dbe.op and lookup for a range in their

        // build range
        this.min = parseFloat(this.get('min'));
        this.max = parseFloat(this.get('max'));
        this.values = this.get('values');
        this.step = parseFloat(this.get('step'));
        this.unit = this.get('unit');
        this.order = 'numeric';
        this.vs = new fun.Set();

        this.isEmpirical = this.values ? true : false;

        this.update();
    }
    op.Range.defs = {};

    /**
     * @returns the parameter p for this opRange
     */
    op.Range.prototype.get = function(p) {
        var v;
        if (this.dblN && (v = this.dblN.get(p))) {
            return v;
        } 
        return '';
    }


    /**
     * @returns the value in range corresponding to normedValue in [0;1]
     */
    op.Range.prototype.getValue = function(normedValue) {
        var value = '';
        if (this.isEmpirical && !fun.isNumeric(this.vs.first())) {
            if (this.vs.size() > 0) {
                var index = Math.floor(normedValue * this.vs.size());
            }
            value = this.vs.item(index) + '';
        } else {
            var size = Math.abs(this.max - this.min);
            value = size * normedValue;
            if (this.step) {
                value = value / this.step;
                var floor = Math.floor(value);
                var ceil = Math.ceil(value);
                if (value - floor < ceil - value) {
                    // closer to floor
                    value = floor;
                } else {
                    value = ceil;
                }
                value = value * this.step;
                value = value + this.min;
            }
        }
        // if (normedValue > 1 || normedValue < 0) console.log('op.Range.getValue ERROR', this, normedValue, value, "/", this.min, this.max, this.step, index);
        return value;
    }

    /**
     * @returns the normedValue in [0;1] corresponding to value in range
     */
    op.Range.prototype.getNormedValue = function(value) {
        value = this.deunitize(value);
        var normedValue = value;
        if (this.isEmpirical && !fun.isNumeric(this.vs.first())) {
            if (parseFloat(value)) {
                value = parseFloat(value);
            }
            if (this.vs.has(value)) {
                var index = this.vs.index(value);
                normedValue = index / this.vs.size();
            }
        } else {
            var size = this.max - this.min;
            if (size > 0) {
                normedValue = (value - this.min) / size;
            } else {
                normedValue = 0;
            }
        }
        if (normedValue > 1 || normedValue < 0) {
            // console.log('op.Range.getNormedValue ERROR', this.uid, value, normedValue, "/", this.min, this.max, this.step, size, this.dblN.xmlElt, index, this);
            if (normedValue > 1) {
                normedValue = 1;
            } else {
                normedValue = 0;
            }
        }
        return normedValue;
    }

    /**
     * adds the unit to a numerical
     */
    op.Range.prototype.unitize = function(value) {
        return value + this.unit;
    }

    /**
     * removes the unit from a unitized value
     */
    op.Range.prototype.deunitize = function(value) {
        value = value + "";
        var index = value.indexOf(this.unit);
        if (index > 0) {
            value = value.substring(0, index);
        }
        return value;
    }

    /**
     * @updates the values for this range
     */
    op.Range.prototype.update = function() {
        // update only once per update wave!
        if (this.state.isUpdating() || this.state.updateStamp > op.rootOpB.state.updateStamp) {
            return;
        }
        
        //  console.log(op.rootOpB.state.updateStamp,'op.Range.update',this.uid,this.state.updateCount,this.state.updateStamp,this.dblN.xmlElt);
        this.state.nowUpdating();
        if (this.isEmpirical) {
            // get nodeSet
            var opNs = this.host.scope.get(this.values);
            // reset value set
            this.vs = new fun.Set();
            // get values for each node
            var that = this;
            opNs.each(function(opN) {
                var v = opN.getValue();
                // TODO 0.3 refactor this into isNumeric : parseInt("20 dogs") === 20 !!!!!
                if (fun.isNumeric(v)) {
                    v = parseFloat(v);
                }
                that.vs.add(v);
            });
            if (!this.dblN.has('min') && parseFloat(this.vs.min())) {
                this.min = parseFloat(this.vs.min());
            }
            if (!this.dblN.has('max') && parseFloat(this.vs.max())) {
                this.max = parseFloat(this.vs.max());
            }
        }
        this.state.nowUpdated();
    }

    /**
     * OpNodeQuery constructor
     * @returns a new OpNodeQuery
     */
    op.NQ = function(dblN, parOp) {
        log.register(this);
        // console.log('op.NQ',dblN,dblN.xmlElt,this,parOp);
        this.events = new evt.EventHandler(this);
        this.state = new op.State(this);
        this.dblN = dblN;
        this.parOp = parOp;
        this.refedOpNQ = null;
        this.opNs = new fun.Set();
        this.domNs = new fun.Set();
        this.scope = this.parOp.scope;
        this.range = null;

        var that = this;        

        this.events.on('OpNsAdded',
            function(ev) {
                op.rootOpB.update();
            });
        if (this.parOp) {
            this.parOp.addOpNQ(this);
        }

        var dblNs = this.dblN.getDblNs();
        dblNs[dbl.RANGE].each(function(dblN) {
            var values;
            if ((values = dblN.get('values'))) { // empirical range
                var root = values.split('.');
                root = root[0];
                var rangeHost = that.scope.get(root).first();
                if (rangeHost) {
                    if (!rangeHost.ranges[dblN.uid]) {
                        var newRange = new op.Range(dblN, rangeHost);
                        rangeHost.ranges[dblN.uid] = newRange;
                    // console.log('new Range',newRange.uid,newRange.dblN.xmlElt,rangeHost.uid,that.uid);
                    }
                    that.range = rangeHost.ranges[dblN.uid];
                } else {
                    console.log('new empirical range FAILED',root,values,rangeHost,that.scope,that.scope.get(root));
                }
            } else { // theorical range
                if (!op.theoricalRanges[dblN.uid]) {
                    op.theoricalRanges[dblN.uid] = new op.Range(dblN);
                // console.log('new theoretical range',dblN.xmlElt);
                }
                that.range = op.theoricalRanges[dblN.uid];
            }
        });

        this.update();
    // first update upon creation
    }

    /**
     * @return wether this opNQ is of given type
     */
    op.NQ.prototype.is = function(type) {
        return this.dblN.is(type);
    }

    /**
     * @return wether this opNQ is a reference
     */
    op.NQ.prototype.isRef = function() {
        return this.dblN.get('ref');
    }

    /**
     * @return the id of this opNQ
     */
    op.NQ.prototype.getId = function() {
        // first check if it has an id
        var dotId = '';
        dotId = this.dblN.get('id');
        // if not then check if it has a ref
        if (!dotId && this.isRef()) {
            dotId = this.dblN.get('ref');
            if (dotId.indexOf('.') > 0) {
                dotId = dotId.split('.');
                var last = 0;
                if (dotId.length > 1) {
                    last = dotId.length - 1;
                }
                dotId = dotId[last];
            }
        }
        return dotId;
    }

    /**
     * adds uniquely a given opN to this opNQ
     * @return nothing
     */
    op.NQ.prototype.addOpN = function(opN) {
        if (this.opNs.add(opN)) {
            opN.inOpNQs.add(this);
            // console.log('op.NQ.addOpN',opN.dblN.xmlElt);
            this.domNs.add(opN.domN);
            this.events.fire(new evt.Event('OpNAdded', this, {
                addedOpN: opN
            }));
        }
    }

    /**
     * removes a given opN from this opNQ sub opNs
     * @return nothing
     */
    op.NQ.prototype.remOpN = function(opN) {
        if (this.opNs.rem(opN)) {
            this.domNs.rem(opN.domN);
            this.events.fire(new evt.Event('OpNRemoved', this, {
                removedOpN: opN
            }));
        }
    }

    /**
     * attempts to solve a missing-opN-problem in opB,
     * i.e. to generate/clone and paste/place missing opNs
     * and add them to opB
     * @return nothing
     */
    op.NQ.prototype.missingOpNs = function(opB, misses) {
        var stratParam = this.dblN.get('miss');
        if (!stratParam) {
            return;
        }
        // console.log('opNQ.missingOpNs',misses,this,this.dblN.xmlElt,opB.dblB.xmlElt);
        // defining strategies
        var strategies = {};
        strategies['clas'] = function(opNQ) {
            // clas strategy : clone latest node as sibling
            if (opNQ.state.isUpdating()) {
                return null;
            }
            var clonedOpN = opNQ.opNs.last();
            if (clonedOpN) {
                var clonedDomN = clonedOpN.domN;
                var cloneDomN = clonedDomN.cloneNode(true);
                clonedDomN.parentNode.appendChild(cloneDomN);
                var opN = new op.N(clonedOpN.dblN, clonedOpN.parOpNQ, cloneDomN,clonedOpN.adapter);
                clonedOpN.parOpNQ.domNs.add(clonedDomN);
                clonedOpN.parOpNQ.addOpN(opN);
                return opN;
            }
            return null;
        };
        var strategy = strategies[stratParam];
        var added = 0;
        // applying strategy a 'misses' number of times
        for (var i = 0; i < misses; i++) {
            var opN = strategy(this, misses);
            if (opN) {
                if (opB.addOpN(opN, this)) {
                    added = added + 1;
                }
            }
        }
    }

    /**
     * updates this opNQ by performing the query / reference query
     * @return nothing
     */
    op.NQ.prototype.update = function() {
        // console.log('op.NQ.update',this.uid,this,this.dblN.xmlElt,this.state.s,this.opNs.size());
        if (this.state.isUpdating()) {
            // if it's already updating, wait!
            this.state.toUpdate();
        } else {
            this.state.nowUpdating();

            if (this.isRef()) {
                this.updateRef();
                return;
            }

            if (this.dblN.get('src')) {
                this.updateD();
                return;
            }

            this.updateE();

            if (this.range) {
                this.range.update();
            }

        }
    }

    /**
     * updates the opNs of this opNQ
     * @return nothing
     */
    op.NQ.prototype.updateOpNs = function() {
        this.opNs.each(function(opN) {
            opN.update();
        });
    }

    /**
     * updates this Ref opNQ
     * @return nothing
     */
    op.NQ.prototype.updateRef = function() {
        var added = false;
        var that = this;
        var ref = this.dblN.get('ref');
        // TODO 0.3 generalize this to any dblN get by passing to dblN.get the scope etc.
        ref = evl.parse(ref,this.scope,null,null,this);
        // console.log('ref evaled',this.dblN.get('ref'),ref);
        var queriedOpNs = this.scope.get(ref);
        // console.log('op.NQ.updateRef results',ref,that.scope,that.dblN.xmlElt,queriedOpNs.size(),queriedOpNs);        
        queriedOpNs.each(function(opN) {
            if (!that.opNs.has(opN) && that.parOp) {
                if (that.parOp.bindOpN) { // it's a bind parent'
                    var bound = that.parOp.bindOpN(opN, that);
                    that.addOpN(opN);
                    added = true;
                } else { // it's a node parent'
                    that.parOp.scope.add(that.getId(),opN);
                }
            }
        });
        this.state.nowUpdated();
        this.updateOpNs();
        if (added) {
            this.events.fire(new evt.Event('OpNsAdded', this));
        }
    // console.log('op.NQ.updateRef',that.scope,that.opNs,that.dblN.xmlElt);
    }

    // TODO 0.3 refactor updateD and updateE and updateRef in a single update with sources

    /**
     * updates this Doc opNQ
     * @return nothing
     */
    op.NQ.prototype.updateD = function() {
        var that = this;
        if (this.dblN.get('src') && this.opNs.size() === 0) {
            log.time['loadSrcStart'+this.dblN.get('src')] = new Date().getTime();
            this.srcAdapter().loadSrc(this.dblN.get('src'),
                function(xmlDoc) {
                    // console.log('op.NQ.updateD',that,that.dblN.xmlElt,xmlDoc);
                    log.time['loadSrcEnd'+that.dblN.get('src')] = new Date().getTime();
                    var opN = new op.N(that.dblN, that, xmlDoc.documentElement);
                    if (that.parOp && that.parOp.bindOpN) {
                        var bound = that.parOp.bindOpN(opN, that);
                    }
                    that.state.nowUpdated();
                    that.events.fire(new evt.Event('OpNsAdded', this));
                });
        } else {
            that.state.nowUpdated();
        }
    }


    /**
     * updates this Elt opNQ
     * @return nothing
     */
    op.NQ.prototype.updateE = function() {
        // console.log('op.NQ.updateE',this,this.dblN.xmlElt);
        var added = false;
        var that = this;
        var query = this.dblN.get('query');
        // parses the query and evaluates scripts calls
        query = evl.parse(query,this.scope,null,null,this);
        if (query) {
            var contextDomN = this.parOp.domN ? this.parOp: null;
            var queriedDomNs = this.queryAdapter().query(query,contextDomN,this);
            var newDomNs = queriedDomNs.sub(this.domNs);
            // console.log('op.NQ.updateE',query,contextDomN,queriedDomNs.size(),queriedDomNs.s,newDomNs.size(),newDomNs.s);
            // TODO 0.3 also compute nodes to remove and call them roteDomNs, they should be taken away from bindings
            newDomNs.each(function(domN) {
                if (that.domNs.add(domN)) {
                    var opN = new op.N(that.dblN, that, domN, this.adapter);
                    if (that.parOp && that.parOp.bindOpN) {
                        var bound = that.parOp.bindOpN(opN, that);
                    }
                    added = true;
                }
            });
            this.state.nowUpdated();
            if (added) {
                this.events.fire(new evt.Event('OpNsAdded', this));
            }
        }
    }


    /**
     * @return the query adapter for this opNQ, acocrding to its query-type
     */
    op.NQ.prototype.queryAdapter = function() {
        var type = this.dblN.get('query-type');
        return src.adapters[type];
    }


    /**
     * @return the query adapter for this opNQ, acocrding to its query-type
     */
    op.NQ.prototype.srcAdapter = function() {
        var type = this.dblN.get('src-type');
        return src.adapters[type];
    }


    /**
     * OpNode Constructor
     * @returns a new OpNode
     * TODO 0.3 optimize (costs 10%)
     */
    op.N = function(dblN, parOpNQ, domN, adapter) {
        log.register(this);
        // console.log('op.N',dblN.xmlElt,parOpNQ,domN);
        this.dblN = dblN;
        this.parOpNQ = parOpNQ;
        this.domN = domN;
        this.adapter = adapter;
        this.events = new evt.EventHandler(this);
        this.state = new op.State(this);
        this.opNQs = new fun.Set();
        this.inOpNQs = new fun.Set();
        this.inOpBs = new fun.Set();
        this.scope = new op.Scope(parOpNQ.scope);
        this.ranges = {};
        this.scope.add('_self', this);
        if (this.parOpNQ) {
            this.parOpNQ.addOpN(this);
            this.scope.add(this.parOpNQ.getId(), this);
        }
        this.buildOpNQs();

        var that = this;
        if (this.domN) {
            if (!domN.dbeEvents) {
                this.domN.dbeEvents = new evt.EventHandler(this.domN);
            }
            this.domN.dbeEvents.on('DomNModified',
                function(ev) {
                    that.events.fire('OpNModified');
                });
        }

        this.dblN.events.on('DblNAdded',
            function(ev) {
                var addedDblN = ev.bag.addedDblN;
                if (ev.host === that.dblN) {
                    if (addedDblN.is(dbl.NODE)) {
                        new op.NQ(addedDblN, that);
                    }
                }
            });
    }

    /**
     * updates the opN
     * @returns nothing
     */
    op.N.prototype.update = function() {
        // console.log('op.N.update',this,this.dblN.xmlElt,this.state.s);
        if (this.state.isUpdating()) {
            this.state.toUpdate();
        } else {
            this.state.nowUpdating();
            this.opNQs.each(function(opNQ) {
                opNQ.update();
            });
            this.state.nowUpdated();
        }
    }

    /**
     * @returns the range of an opN in the potential context of an opNQ
     */
    op.N.prototype.range = function(opNQ) {
        return (opNQ && opNQ.range) ? opNQ.range : this.parOpNQ.range;
    }


    /**
     * adapts the opN in the context of an opB and an opNQ
     * @returns nothing
     */
    op.N.prototype.adapt = function(opB, opNQ) {
        // 1. get adapt string
        var expr = opNQ.dblN.get('adapt');
        if (!expr) {
            return;
        }
        var scope = opB.scope;
        var oldValue = this.getValue();

        // 3. evaluate adaptation in scope context
        var value = evl.evaluate(expr, scope, this, opB, opNQ);
        var uValue = this.range(opNQ).unitize(value);
        this.setValue(uValue);
        var newValue = this.getValue();
        if (oldValue !== newValue) {
    // console.log('op.N.adaptED',scope.bag,this.uid,this.dblN.xmlElt,opNQ.dblN.xmlElt,expr,this.domN,oldValue,'>>',value,uValue,':',this.getValue());
    // op.rootOpB.update(); // removed from now on, doesn't seem to hurt to much the process
    }
    }

    /**
     * returns the value of an opN
     * @returns void
     * TODO 0.3 now value caching: cache emptied when domN Modified
     */
    op.N.prototype.getValue = function() {
        var v;
        v = this.parOpNQ.queryAdapter().getValue(this);
        // console.log('op.N.getValue',this.parOpNQ.queryAdapter().getValue,this.domN,v);
        if (v === null) {
            if (this.parOpNQ.range) {
                v = this.parOpNQ.range.min;
            }
        // console.warn('op.N.getValue USING MIN',this.parOpNQ.dblN.xmlElt,this.parOpNQ.queryAdapter().getValue(this),this.domN,v);
        }
        return v;
    }

    /**
     * sets the value of an opN to a given v
     * @returns void
     */
    op.N.prototype.setValue = function(v) {
        this.parOpNQ.queryAdapter().setValue(this,v);
    }

    /**
     * create for each existing sub DblN an opNQ
     * @returns nothing
     */
    op.N.prototype.buildOpNQs = function() {
        // create for each existing sub DblN an opNQ
        var that = this;
        var dblNs = this.dblN.getDblNs();
        var buildOpNQ = function(dblN) {
            new op.NQ(dblN, that);
        }
        dblNs[dbl.NODE].each(buildOpNQ);
    }

    /**
     * adds uniquely a given opNQ to this opN
     * @returns nothing
     */
    op.N.prototype.addOpNQ = function(opNQ) {
        if (this.opNQs.add(opNQ)) {
            var id;
            var that = this;
            if ((id = opNQ.getId())) {
                this.scope.set(id, opNQ.opNs);
            }
            this.events.fire(new evt.Event('OpNQAdded', this, {
                addedOpNQ: opNQ
            }));
            opNQ.events.on('OpNsAdded',
                function(ev) {
                    that.events.fire(new evt.Event('OpTreeModified', that));
                    op.rootOpB.update();
                });
        }
    }

    /**
     * removes a given opNQ to this opN
     * @returns nothing
     */
    op.N.prototype.remOpNQ = function(opNQ) {
        if (this.opNQs.rem(opNQ)) {
            var id;
            if ((id = opNQ.getId())) {
                this.scope.unset(id);
            }
            this.events.fire(new evt.Event('OpNQRemoved', this, {
                addedOpNQ: opNQ
            }));
        }
    }

    /**
     * @evaluates for this opN the match attribute of give opNQ
     */
    op.N.prototype.getMatchKey = function(opNQ) {
        var opNKey = opNQ.dblN.get('match');
        if (opNKey) {
            opNKey = evl.evaluate(opNKey, this.scope, this, null, opNQ);
            if (opNKey && opNKey.getValue) {
                return opNKey.getValue();
            } 
        }
        return opNKey;
    }

    /**
     * OpBindQuery creator
     * @return a new OpBindQuery
     */
    op.BQ = function(dblB, parOpB) {
        log.register(this);
        // console.log('op.BQ',this,dblB.xmlElt,parOpB);
        this.events = new evt.EventHandler(this);
        this.state = new op.State(this);
        this.parOpB = parOpB;
        this.dblB = dblB;
        this.opNs = new fun.Set();
        this.opBs = new fun.Set();
        this.opNQs = new fun.Set();
        this.scope = this.parOpB.scope;
        var that = this;

        // event listenings
        dblB.events.on('DblNAdded',
            function(ev) {
                var addedDblN = ev.bag.addedDblN;
                if (ev.target === that.dblB) {
                    if (!addedDblN.is(dbl.BIND)) {
                        new op.NQ(addedDblN, that);
                    }
                }
            });
        if (parOpB) {
            parOpB.addOpBQ(this);
        }
        this.buildOpNQs();
    }

    /**
     * add uniquely a given opB to this opBQ
     * @returns nothing
     */
    op.BQ.prototype.addOpB = function(opB) {
        if (this.opBs.add(opB)) {
            var that = this;
            this.events.fire(new evt.Event('OpBAdded', this, {
                addedOpB: opB
            }));
        }
    }

    /**
     * removes a given opB from this opBQ
     * @returns nothing
     */
    op.BQ.prototype.remOpB = function(opB) {
        if (this.opBs.rem(opB)) {
            this.events.fire(new evt.Event('OpBRemoved', this, {
                removedOpB: opB
            }));
        }
    }

    /**
     * check the opNs of an opB to see what's missing and try to complete it
     * @returns nothing
     */
    op.BQ.prototype.completeOpB = function(opB) {
        // console.log('op.BQ.completeOpB',this,this.dblB.xmlElt,opB,opB.dblB.xmlElt);
        this.opNQs.each(function(opNQ) {
            var min = parseInt(opNQ.dblN.get('min'));
            var id = opNQ.getId();
            if (!opB.opNs[id] || (opB.opNs[id].size() < min)) {
                missings = true;
                var size = opB.opNs[id] ? opB.opNs[id].size() : 0;
                var miss = min - size;
                opNQ.missingOpNs(opB, miss);
            }
        });
    }


    /**
     * add uniquely a given opNQ to this opBQ
     * @returns nothing
     */
    op.BQ.prototype.addOpNQ = function(opNQ) {
        if (this.opNQs.add(opNQ)) {
            var id;
            var that = this;
            opNQ.events.on('OpNsAdded',
                function(ev) {
                    that.events.fire(new evt.Event('OpTreeModified', that));
                });
            this.events.fire(new evt.Event('OpNQAdded', this, {
                addedOpNQ: opNQ
            }));
        }
    }

    /**
     * removes a given opNQ from this opBQ
     * @returns nothing
     */
    op.BQ.prototype.remOpNQ = function(opNQ) {
        if (this.opNQs.rem(opNQ)) {
            var id;
            if ((id = opNQ.getId())) {
                this.scope.unset(id);
            }
            this.events.fire(new evt.Event('OpNQRemoved', this, {
                removedOpNQ: opNQ
            }));
        }
    }

    /**
     * create for each existing sub DblN an opNQ
     * @returns nothing
     */
    op.BQ.prototype.buildOpNQs = function() {
        // create for each existing sub DblN an opNQ
        // console.log('op.BQ.buildOpNQs',this.dblB.xmlElt);
        var that = this;
        var dblNs = this.dblB.getDblNs();
        dblNs[dbl.NODE].each(function(item) {
            new op.NQ(item, that);
        });
        dblNs[dbl.RANGE].each(function(item) {
            new op.NQ(item, that);
        });
    }

    /**
     * opN hello please binding algorithm
     * @return wether binding succeeded
     */
    op.BQ.prototype.bindOpN = function(opN, opNQ) {
        if (this.opNs.has(opN)) {
            // console.log('!!! opN already bound',opN);
            return true;
        }
        // console.log('op.BQ.bindOpN',opN,opN.parOpNQ.dblN.xmlElt,opNQ.dblN.xmlElt,this.dblB.xmlElt);
        var res = false;
        // hello round
        this.opBs.each(function(opB) {
            if (!res) {
                res = opB.addOpN(opN, opNQ, false);
            // simply say hello
            }
        });

        // please round
        if (!res) {
            this.opBs.each(function(opB) {
                if (!res) {
                    res = opB.addOpN(opN, opNQ, true);
                // insist and say please
                }
            });
        }
        // else round
        if (!res) {
            var opB = new op.B(this.dblB, this);
            res = opB.addOpN(opN, opNQ, true);
        }

        return res;
    }


    /**
     * update an opBQ, because something deeper changed
     * @returns nothing
     */
    op.BQ.prototype.update = function() {
        // console.log('op.BQ.update',this.uid,this.dblB.xmlElt);
        this.opNQs.each(function(opNQ) {
            opNQ.update();
        });
        var opNQsUpdated = true;
        this.opNQs.each(function(opNQ) {
            opNQsUpdated = (opNQsUpdated && opNQ.state.isUpdated());
        });
        // console.log('op.BQ.update opNQsUpdated=',opNQsUpdated,this,this.dblB.xmlElt,this.state.s);
        if (opNQsUpdated) {
            this.opBs.each(function(opB) {
                opB.update();
            });
        }
    }


    /**
     * OpBind creator
     * @return a new OpBind
     */
    op.B = function(dblB, parOpBQ) {
        log.register(this);
        // console.log('op.B',this.uid,dblB,dblB.xmlElt,parOpBQ);
        this.events = new evt.EventHandler(this);
        this.state = new op.State(this);
        this.dblB = dblB;
        this.keys = new fun.Set();
        this.parOpBQ = parOpBQ;
        this.opBQs = new fun.Set();
        this.opNs = {};
        this.ranges = {};

        if (this.parOpBQ) {
            this.scope = new op.Scope(this.parOpBQ.scope);
            parOpBQ.addOpB(this);
        } 

        var id;
        if ((id = this.dblB.get('id'))) {
            this.scope.add(id,this);
        }

        this.buildOpBQs();
        var that = this;
        this.dblB.events.on('DblNAdded',
            function(ev) {
                var addedDblN = ev.bag.addedDblN;
                if (ev.target === that.dblB) {
                    if (addedDblN.is(dbl.BIND)) {
                        new op.BQ(addedDblN, that);
                    }
                }
            });

    }


    /**
     * temptatively adds a given opN to this opB opNs set as result of given opNQ
     * @returns wether the opN has been added or not
     */
    op.B.prototype.addOpN = function(opN, opNQ, please) {
        // console.log('op.B.addOpN',this.dblB.xmlElt,opN.dblN.xmlElt,opNQ,please);
        please = please ? true: false;
        var id = opNQ.getId();
        var count = 0;
        var thresholdName = please ? 'max': 'min';
        var thresholdValue = parseInt(opNQ.dblN.get(thresholdName));
        if (this.opNs[id]) {
            count = this.opNs[id].size();
        }

        if (count < thresholdValue && this.match(opN, opNQ)) {
            if (!this.opNs[id]) {
                this.opNs[id] = new fun.Set();
            }
            if (this.opNs[id].add(opN)) {
                // added!
                // console.log('added',this.scope.bag,this.dblB.xmlElt,opNQ.dblN.xmlElt,opN.domN,count,thresholdValue,this.opNs[id].size());
                this.scope.add(id, opN);
                this.parOpBQ.opNs.add(opN);
                opN.inOpBs.add(this);
                var matchKey = opN.getMatchKey(opNQ);
                if (matchKey) {
                    this.keys.add(matchKey);
                }
                var that = this;
                if (opNQ.dblN.get('adapt')) {
                    this.events.on('OpBComplete',
                        function(ev) {
                            // console.log('opBComplete >> opN.adapt:',that.isComplete(),that.uid,that.opNs,that.dblB.xmlElt,opN.uid,opN.dblN.xmlElt);
                            opN.adapt(that,opNQ);
                        });
                }
                opN.events.on('OpNModified',
                    function(ev) {
                        that.scope.bag['_latest'] = opN;
                        // op.rootOp
                        // B.update();
                        if (that.isComplete()) {
                            that.events.fire('OpBComplete');
                        // tells opNs they can adapt
                        }
                    });
                this.events.fire(new evt.Event('OpNAdded', this, {
                    addedOpN: opN
                }));
                return true;
            }
            console.log('not added', count, thresholdValue, this.opNs[id].size());
        }

        return false;

    }

    /**
     * tests wether an opB is complete or not
     * @returns nothing
     */
    op.B.prototype.isComplete = function() {
        var complete = true;
        var that = this;
        this.parOpBQ.opNQs.each(function(opNQ) {
            var min = parseInt(opNQ.dblN.get('min'));
            var id = opNQ.getId();
            if (!that.opNs[id] && min > 0) {
                complete = false;
            }
            if (that.opNs[id] && (that.opNs[id].size() < min)) {
                complete = false;
            }
        });
        // console.log('op.B.isComplete',complete,this.opNs,this.dblB.xmlElt);
        return complete;
    }

    /**
     * removes a given opN from this opB
     * @returns nothing
     */
    op.B.prototype.remOpN = function(opN) {
        if (this.opNs.rem(opN)) {
            var that = this;
        }
    }

    /**
     * attempts to key match this opB with a given opN
     * @returns wether matching of given opN with this opB succeeded
     */
    op.B.prototype.match = function(opN, opNQ) {
        var opNKey = opN.getMatchKey(opNQ);
        var res;
        if (!opNKey || this.keys.size() === 0) {
            res = true;
        }
        else {
            res = opNKey ? this.keys.has(opNKey) : true;
        }
        // if (this.keys.size()) console.log('op.B.match',this.keys.s,opN.getMatchKey(opNQ),res,opN.dblN.xmlElt,opN.domN,opNQ.dblN.xmlElt,this.opNs,this.scope);
        return res;
    }


    /**
     * adds uniquely a given opBQ to this opB
     * @returns nothing
     */
    op.B.prototype.addOpBQ = function(opBQ) {
        if (this.opBQs.add(opBQ)) {
            var that = this;
            this.events.fire(new evt.Event('OpBQAdded', this, {
                addedOpBQ: opBQ
            }));
        }
    }

    /**
     * removes a given opBQ from this opB
     * @returns nothing
     */
    op.B.prototype.remOpBQ = function(opBQ) {
        if (this.opBQs.rem(opBQ)) {
            this.events.fire(new evt.Event('OpBQRemoved', this, {
                removedOpBQ: opBQ
            }));
        }
    }

    /**
     * create for each existing sub DblB an opBQ
     * @returns nothing
     */
    op.B.prototype.buildOpBQs = function() {
        // create for each subDblBs an opBQ
        var subDblBs = this.dblB.getDblNs();
        subDblBs = subDblBs[dbl.BIND] ? subDblBs[dbl.BIND] : new fun.Set();
        // console.log('op.B.buildOpBQs',this.dblB.xmlElt,subDblBs,subDblBs.size());
        var that = this;
        subDblBs.each(function(dblB) {
            new op.BQ(dblB, that);
        });
    }

    /**
     * update each sub opBQ of this opB
     * @returns nothing
     */
    op.B.prototype.update = function() {
        // console.log('op.B.update',this,this.dblB.xmlElt,this.state.s + '-' + this.state.updateCount,this.opNs);
        if (this.state.isUpdating()) {
            this.state.toUpdate();
        } else {
            this.state.nowUpdating();
            if (!this.isComplete()) {
                this.parOpBQ.completeOpB(this);
            }
            if (this.isComplete()) {
                // console.log('op.B.update COMPLETE');
                this.events.fire('OpBComplete');
                // tells opNs they can adapt
                this.opBQs.each(function(opBQ) {
                    opBQ.update();
                });
            }
            this.state.nowUpdated();
            // if (this.state.isToUpdate() && (this.isRoot) && (this.state.toUpdateCount > this.state.updateCount)) {
            if (this.isRoot) {
                if (this.state.isToUpdate()) {
                    this.update();
                } else {
                    log.time['rootUpdateDone'] = new Date().getTime();
                }
            }
        }
    }


    /**
     * renew op.rootOpB
     * @return nothing
     */
    op.renewRootOpB = function() {
        var rootDblB;
        rootDblB = new dbl.N(null, null);
        op.rootOpB = new op.B(rootDblB, null);
        // override some function declarations
        op.rootOpB.isRoot = true;
        op.rootOpB.scope = new op.Scope();
        op.rootOpB.isComplete = function (){
            return true;
        };
    }

    /**
     * adaptation expression evaluator function
     * @returns the result of evaluating an expression in the context of a scope
     */
    evl.evaluate = (function() {

        // generate stats functions once!
        var _context = {};

        var _resolve = function(input) {
            if (typeof input === 'number') {
                var res = new fun.Set();
                res.add(input);
                return res;
            }
            if (typeof input === 'string') {
                return _context.scope.get(input);
            }
            else return input;
        }

        var size = function(set) {
            set = _resolve(set);
            return set.size();
        }

        var mean = function(set) {
            set = _resolve(set);
            var res = set.mean();
            return res;
        }

        var index = function(item, set) {
            item = _resolve(item).first();
            set = _resolve(set);
            var res = set.index(item);
            return res;
        }

        var latest = function() {
            var set = new fun.Set();
            set.add(_context.scope.get('_latest'));
            return set;
        }

        var id = function(set) {
            set = _resolve(set);
            var opN = set.first();
            return opN.parOpNQ.getId();
        }

        var nvalue = function(set) {
            set = _resolve(set);
            var opN = set.first();
            if (opN) {
                return opN.range().getNormedValue(opN.getValue());
            }
            return '';
        }

        var value = function(set) {
            set = _resolve(set);
            var opN = set.first();
            if (opN) {
                return opN.getValue();
            }
            return '';
        }

        // TODO 0.3 useless? to remove
        var values = function() {
            var id = _context.opN.parOpNQ.getId();
            var index = _context.scope.get(id).index(_context.opN);
            var set = arguments[index];
            return value(set);
        }

        // TODO 0.3 useless? to remove
        var strings = function() {
            var id = _context.opN.parOpNQ.getId();
            var index = _context.scope.get(id).index(_context.opN);
            return arguments[index];
        }

        var lt = function(s1, s2) {
            var v1 = _resolve(s1).first().getValue();
            var v2 = _resolve(s2).first();
            var res = (v1 < v2) ? 0 : 1;
            return res;
        }

        var map = function(input, options) {
            var set = _resolve(input);
            // console.log('map',input,set.s);
            var sRange;
            var sValue;
            if (typeof set.first() === 'number') {
                sRange = {
                    getNormedValue: function(v) {
                        return v;
                    }
                }
                sValue = set.first();
            } else {
                var opN = set.first();
                if (opN) {
                    sRange = opN.range();
                    _context.opB.parOpBQ.opNQs.each(function(opNQ){
                        if (opN.inOpNQs.has(opNQ)) {
                            sRange = opN.range(opNQ);
                        }
                    });
                    sValue = opN.getValue();
                } else {
                    // console.log(input,set,_context.scope);
                    return '';
                }
            }
            var sNormedValue = sRange.getNormedValue(sValue);
            if (options) {
                options = options.split(',');
                for (var i = 0; i < options.length; i++) {
                    sNormedValue = map.options[options[i]](sNormedValue);
                }
            }
            var tRange = _context.opN.range(_context.opNQ);
            var tNewValue = tRange.getValue(sNormedValue);
            // console.log('map DONE',input,set,sValue,sNormedValue,'>>',tNewValue,sRange.dblN.xmlElt,sRange,tRange);
            return tNewValue;
        };
        map.options = {};
        map.options['inv'] = function(nv) {
            return 1 - nv;
        };
        map.options['log'] = function(nv) {
            return Math.log(nv);
        };
        map.options['exp'] = function(nv) {
            return Math.exp(nv);
        };
        map.options['pow2'] = function(nv) {
            return Math.pow(nv, 2);
        };
        map.options['pow3'] = function(nv) {
            return Math.pow(nv, 3);
        };
        map.options['pow4'] = function(nv) {
            return Math.pow(nv, 4);
        };
        map.options['pow6'] = function(nv) {
            return Math.pow(nv, 6);
        };
        map.options['pow8'] = function(nv) {
            return Math.pow(nv, 8);
        };

        /**
         * adaptation expression evaluator actually exported
         * @returns the result of evaluating an expr in the context of a scope, a current opN, opB and opNQ
         */
        var evaluate = function(expr, scope, opN, opB, opNQ) {
            // 1. prepare context
            _context.expr = expr;
            _context.scope = scope;
            _context.opN = opN;
            _context.opB = opB;
            _context.opNQ = opNQ;

            // 2. eval expr
            var result = eval(expr);
            return result;
        }
        // return evaluator function only
        return evaluate;

    })();

    /**
     * Parses a string and evaluates script parts inside
     * @returns the string with evalutable parts replaced by their values
     */
    evl.parse = function(expr, scope, opN, opB, opNQ) {
        var expr = expr ? expr : '';
        var scope = scope ? scope : null;
        var opN = opN ? opN : null;
        var opB = opB ? opB : null;
        var opNQ = opNQ ? opNQ : null;
        var start = -1;
        var end = -1;
        var resultExpr = expr;
        if ((start = expr.indexOf('{')) > -1 && (end = expr.indexOf('}')) > -1) {
            var subExpr = expr.substring(start+1,end);
            var subValue = evl.evaluate(subExpr,scope,opN,opB,opNQ);
            var resultExpr = expr.substring(0,start) + subValue + expr.substring(end+1);
        }
        // console.log('evl.parse',expr,subExpr,subValue,resultExpr);
        if (resultExpr.indexOf('{') > -1 && resultExpr.indexOf('}') > -1) {
            return evl.parse(resultExpr,scope,opN,opB,opNQ);
        } else {
            return resultExpr;
        }
    };


    // *** dbe.fun : helper functions

    // ***** dbe.src : source management
    src.adapters = {};

    /**
     * loads asynchronously an xml doc at a given url
     * @returns xmlDoc if no callback provided, else returns nothing and passed result to callback
     */
    src.addAdapter = function(term, adapter) {
        src.adapters[term] = adapter;
    }


    /**
     * Event handler constructor for a given host object
     * @returns a new EventHandler
     */
    evt.EventHandler = function(host) {
        log.register(this);
        this.host = host;
        this.listeners = [];
    }
    evt.EventHandler.prototype.on = function(name, callback) {
        if (!this.listeners[name]) {
            this.listeners[name] = [];
        }
        this.listeners[name].push(callback);
    }
    evt.EventHandler.prototype.fire = function(event) {
        if (typeof event === 'string') {
            event = new evt.Event(event, this.host);
        }

        // console.log('ev',event.uid,event.names,'host:',this.host,'event:',event);
        // callback focused listeners per name
        var i;
        var callbacks = this.listeners[event.name];
        if (callbacks) {
            for (i = 0; i < callbacks.length; i++) {
                callbacks[i](event);
            }
        }
    }

    /**
     * Event constructor
     * @returns a new Event
     */
    evt.Event = function(name, target, bag) {
        log.register(this);
        this.name = name;
        this.target = target;
        this.bag = bag;
        this.uid = this.nextUid();
    }
    evt.Event.prototype.nextUid = (function() {
        var uidCounter = 0;
        return function() {
            return uidCounter++;
        }
    })();
    evt.Event.prototype.is = function(name) {
        return this.name === name;
    }

    /**
     * Set constructor
     * @returns a new Set
     */
    fun.Set = function() {
        log.register(this);
        this.s = [];
        this.cache = {};
    }

    /**
     * @return wether item has been added, or wether it already was in it
     */
    fun.Set.prototype.add = function(item) {
        if (this.s.indexOf(item) < 0 && item !== null) {
            this.s.push(item);
            this.cache = {};
            return true;
        }
        return false;
    }

    /**
     * @return wether item has been removed
     */
    fun.Set.prototype.rem = function(item) {
        var i = this.s.indexOf(item);
        if (i >= 0) {
            this.s.splice(i, 1);
            this.cache = {};
            return true;
        }
        return false;
    }

    /**
     * @return the cardinality of the set
     */
    fun.Set.prototype.size = function() {
        return this.s.length;
    }

    /**
     * @return wether item has been removed
     */
    fun.Set.prototype.has = function(item) {
        return (this.s.indexOf(item) >= 0);
    }

    /**
     * applies f to each item of the set
     * @return nothing
     */
    fun.Set.prototype.each = function(fct) {
        this.s.forEach(fct);
    }

    /**
     * @returns the union of two sets
     */
    fun.Set.prototype.union = function(set) {
        var union = new fun.Set();
        var s = this.s;
        var i = 0;
        for (i = 0; i < s.length; i++) {
            union.s.push(s[i]);
        }
        s = set.s;
        for (i = 0; i < s.length; i++) {
            union.add(s[i]);
        }
        return union;
    }


    /**
     * adds to this set the items of the given set
     * @returns nothing
     */
    fun.Set.prototype.join = function(set) {
        var s = set.s;
        for (var i = 0; i < s.length; i++) {
            this.add(s[i]);
        }
    }
    

    /**
     * @returns the intersection of two sets
     */
    fun.Set.prototype.inter = function(set) {
        var inter = new fun.Set();
        var ts = this.s;
        var is = inter.s;
        var ss = set.s;
        var item;
        for (var i = 0; i < ts.length; i++) {
            item = ts[i];
            if ( ss.indexOf(item) >= 0) {
                is.push(item);
            }
        }
        return inter;
    }

    /**
     * @returns the substraction of given set from this set
     */
    fun.Set.prototype.sub = function(set) {
        var sub = new fun.Set();
        var ts = this.s;
        var us = sub.s;
        var ss = set.s;
        for (var i = 0; i < ts.length; i++) {
            item = ts[i];
            if ( ss.indexOf(item) < 0) {
                us.push(item);
            }
        }
        return sub;
    }

    /**
     * @returns wether given set is equal to this set
     */
    fun.Set.prototype.equal = function(set) {
        if (this.size() !== set.size()) {
            return false;
        }
        this.each(function(i) {
            if (!set.has(i)) {
                return false;
            }
        });
        return true;
    }

    /**
     * @returns the first element of this set
     */
    fun.Set.prototype.first = function() {
        return (this.size() > 0) ? this.s[0] : null;
    }

    /**
     * @returns the last element of this set
     */
    fun.Set.prototype.last = function() {
        return (this.size() > 0) ? this.s[(this.s.length - 1)] : null;
    }

    /**
     * @returns the index of given element in this set
     */
    fun.Set.prototype.index = function(item) {
        return this.s.indexOf(item);
    }

    /**
     * @returns the mean of this set items values
     */
    fun.Set.prototype.mean = function() {
        var sum = 0;
        var m = 0;
        for (var i = 0; i < this.size(); i++) {
            var v = this.s[i].getValue();
            if (v === "0") {
                v = 0;
            } else if (parseFloat(v)) {
                v = parseFloat(v);
                sum += v;
            } else if (parseInt(v)) {
                v = parseInt(v);
                sum += v;
            } else {
                console.log('mean item NaN', s[i], v);
            }
        }
        m = sum / this.size();
        // console.log('mean',m, this.s, this.size());
        return m;
    }

    /**
     * @returns the item of given index in this set
     */
    fun.Set.prototype.item = function(index) {
        if (index < this.size()) {
            return this.s[index];
        }
        return null;
    }

    /**
     * returns the max item of this set
     */
    fun.Set.prototype.max = function(comparator) {
        if (!this.cache.max) {
            this.minmax(comparator);
        }
        return this.cache.max;
    }
    
    /**
     * returns the min item of this set
     */
    fun.Set.prototype.min = function(comparator) {
        if (!this.cache.min) {
            this.minmax(comparator);
        }
        return this.cache.min;
    }


    /**
     * copmues the min and max of this set
     */
    fun.Set.prototype.minmax = function(comparator) {
        var min = this.s[0];
        var max = this.s[0];
        for (var i = 1; i < this.s.length; i++) {
            var item = this.s[i];
            min = (item < min) ? item : min;
            max = (item > max) ? item : max;
        }
        this.cache.min = min;
        this.cache.max = max;
    }

    /**
     * orders this set ordered by the given "smaller than" comparator
     * returns nothing
     * REFACTOR using array.sort() function
     */
    fun.Set.prototype.order = function(comparator) {
        var orderedArray = [];
        var lastInsert = null;
        var insertCandidate = null;
        for (var i = 0; i < this.s.length; i++) {
            for (var j = 0; j < this.s.length; j++) {
                var item = this.s[j];
                insertCandidate = ((comparator(lastInsert, item)) && (comparator(item, insertCandidate))) ? item : insertCandidate;
            }
            orderedArray.push(insertCandidate);
            lastInsert = insertCandidate;
            insertCandidate = null;
        }
        this.s = orderedArray;
        this.isOrdered = true;
    // console.log('Set.order',this.s,orderedSet.s);
    }

    /**
     * "Smaller than" omparator functions
     */
    fun.comparators = {};
    fun.comparators.num = function(e1, e2) {
        return (e1 === null || e2 === null || e1 < e2);
    };
    fun.comparators.alpha = function(e1, e2) {
        return (e1 === null || e2 === null || e1 < e2);
    };

    /**
     * @return wether the given string is the representation of a number
     * TODO 0.3 unit test
     */
    fun.isNumeric = function (s) {
        return (parseFloat(s) == s);
    }


    /*
     * global objet counter
     */
    log.counter = 0;

    /*
     * global stats
     */
    log.register = function (o) {
        o['uid'] = log.counter++;
    }

    log.time = {};
    log.timeReport = function () {
        var r = '';
        r += 'bp ';
        r += log.time['rootUpdateDone']-log.time['rootDblN'];
        console.log(r,log.time);
    }


    // *** INITIALIZATION ***
    op.renewRootOpB();

    // fullfill and return API object
    api.addBinding = op.addRootBinding;
    api.addAdapter = src.addAdapter;
    api.reset = op.renewRootOpB;
    api.fun = dbe.fun;
    api.full = dbe;
    return api;
})();